import MBase from "../utils/m-base";
import { IParm, IGlobal, IModel, IProp, IReqMethod, IType } from "../m-types";
import { ISwgDefine, ISwgPaths, ISwgPath, ISwgSchema } from "../m-types";
import { ISwgMethod, ISwgParamter, ISwgBody, ISwgResponse } from "../m-types";
import { IParsedEnum, IParsedMethod } from "../m-types";
import { IParsedModel, IParsedResult } from "../m-types";
import { IParsedUrl, IParsedUse } from "../m-types";

/**Swagger 解析器 */
export default class MParser {
  /**顶级参数 */
  private _parm = {} as IGlobal;
  /**分好组的Swagger Methods */
  private _swgMethods: Array<IArrayGroup<ISwgMethod>> = [];
  /**所有的definintions */
  private _swgDefines: Array<ISwgDefine> = [];

  /**解析后的请求方法 */
  private _methods: Array<IParsedMethod> = [];
  /**解析后的枚举 */
  private _enums: Array<IParsedEnum> = [];
  /**解析后的模型 */
  private _models: Array<IParsedModel> = [];

  /**顶级参数 */
  protected get parm() {
    return this._parm;
  }
  /**计算参数中的swagger数据 */
  protected get swg() {
    return this.parm.swgJson;
  }
  /**计算swagger的版本 */
  protected get ver() {
    const { swagger, openapi } = this.swg;
    const v = swagger || openapi || "";
    return parseInt(v.split(".")[0]) || 0;
  }
  /**计算swagger请求定义数据(分组) */
  protected get swgMethods() {
    return this._swgMethods;
  }
  /**计算swagger模型定义数据 */
  protected get swgDefines() {
    return this._swgDefines;
  }
  /**计算解析后的请求方法 */
  protected get methods() {
    return this._methods;
  }
  /**计算解析后的枚举 */
  protected get enums() {
    return this._enums;
  }
  /**计算解析后的模型 */
  protected get models() {
    return this._models;
  }

  constructor(parm: IGlobal) {
    this._parm = parm;
    this._swgMethods = this.getSwaggerMethods();
    this._swgDefines = this.getSwaggerDefines();
    this.parseSwagger();
    this.fixEnumTypes();
  }
  /**获取解析结果 */
  public parse() {
    const enums = this._enums;
    const models = this._models;
    const methods = this._methods;
    return { enums, models, methods } as IParsedResult;
  }

  /**解析开始 */
  protected parseSwagger() {
    const { swgDefines, swgMethods } = this;
    const mModels = swgDefines.map((x) => this.toModel(x));
    const mEnums = mModels.filter((x) => x.isEnum);
    const mDtos = mModels.filter((x) => !x.isEnum);
    this._enums = mEnums.map((x) => this.toParsedEnum(x));
    this._models = mDtos.map((x) => this.toParsedModel(x));
    this._methods = swgMethods.map((x) => this.toParsedMethod(x));
  }

  /**swg函数组转成ParsedMethod模型 */
  protected toParsedMethod(method: IArrayGroup<ISwgMethod>): IParsedMethod {
    const base = this.parm.apiName;
    const group = method.key;
    const items = method.items || [];
    const funcs = items.map((x) => this.toReqMethod(x));
    const urls = funcs.map(
      (x) =>
        ({
          name: x.urlName,
          value: x.path,
          description: x.description,
        } as IParsedUrl)
    );
    const parms = funcs
      .map((x) => x.inputParms)
      .flat()
      .map((x) => x.type)
      .flat();
    const input = funcs.map((x) => x.inputModel).flat();
    const output = funcs.map((x) => x.outputModel).flat();
    const types = [...parms, ...input, ...output].distinct("type");
    const uses = this.getUsedTypes(types);

    return { base, group, uses, urls, funcs } as IParsedMethod;
  }

  /**swg请求方法转成reqMethod模型 */
  protected toReqMethod(parm: ISwgMethod): IReqMethod {
    const { method, path, summary } = parm;
    const { parameters, requestBody, responses } = parm;
    const name = this.getMethodName(parm);
    const urlName = `url${MBase.camelUpperCase(name)}`;
    const description = summary || path || "";
    const inputParms = this.getMethodParms(parameters);
    const inputModel = this.getMethodBody(requestBody || parameters);
    const outputModel = this.getMethodResponse(responses["200"]);
    const mOpt1 = { name, description, path, urlName, method };
    const mOpt2 = { inputParms, inputModel, outputModel };
    return { ...mOpt1, ...mOpt2 } as IReqMethod;
  }

  /**定义模型转中间模型 */
  protected toModel(parm: ISwgDefine): IModel {
    const type = parm.type;
    const enumList = parm.enum;
    const requireds = parm.required || [];
    const properties = parm.properties || {};
    const name = this.getBaseGenericName(parm.name);
    const mDesc = parm.description || name;
    if (enumList) {
      const props = enumList.map((e) => ({
        name: `${e}`,
        type: [{ type, isEnum: false }] as Array<IType>,
      }));
      return { name, props, isEnum: true, description: mDesc } as IModel;
    } else {
      const props: Array<IProp> = [];
      for (const p in properties) {
        const pName = MBase.camelLowercase(p);
        const property = properties[p];
        const nullable = property.nullable === false ? false : true;
        const description = property.description || "";
        const required = requireds.includes(p);
        const type = this.getSchemaTypes(property);
        props.push({ name: pName, description, required, nullable, type });
      }
      return { name, props, isEnum: false, description: mDesc } as IModel;
    }
  }
  /**swg中间模型转成最终模型 */
  protected toParsedModel(model: IModel) {
    const { name, description, props } = model;
    const types = props
      .map((p) => p.type)
      .flat()
      .distinct("type");
    let uses = this.getUsedTypes(types);
    uses = uses.filter((x) => x.name != name);
    return { name, description, props, uses } as IParsedModel;
  }
  /**swg中间模型转成最终枚举 */
  protected toParsedEnum(model: IModel) {
    const name = model.name;
    const description = model.description || "";
    const ps = model.props || [];
    const type = (((ps[0] || {}).type || [])[0] || {}).type || "string";
    const nums =
      "int,integer,int32,int64,short,long,double,decimal,float".split(",");
    const isNum = nums.includes(type);
    const props = ps.map((p) => {
      const key = isNum ? `v${p.name}` : p.name;
      const value = isNum ? parseFloat(p.name) : p.name;
      return { key, value };
    });
    return { name, type, description, props } as IParsedEnum;
  }

  /**获取schema对应的模型链 */
  protected getSchemaTypes(parm: ISwgSchema): Array<IType> {
    const { swgDefines = [] } = this;
    const { type = "", items, additionalProperties } = parm;
    const { format, schema, $ref, properties, nullable } = parm;
    const description = parm.description || "";
    const pres = "#/definitions/,#/components/schemas/".split(",");
    if ($ref) {
      //优先判是否是引用类型
      let ref = $ref;
      pres.forEach((x) => (ref = ref.replace(x, "")));
      const res = swgDefines.find((d) => d.name === ref);
      if (!res) return [];
      const type = this.getBaseGenericName(res.name);
      return [{ type, nullable, description }];
    } else if (type === "array") {
      //再判是否列表类型
      return [{ type, description }, ...this.getSchemaTypes(items || {})];
    } else if (additionalProperties) {
      //再判是否含有附加属性类型
      return [{ type: "Map", description }];
    } else if (schema) {
      //再判是否含有子链类型
      return this.getSchemaTypes(schema);
    } else if (properties) {
      //再判是否有属性枚举
      return [{ type, description }];
    } else {
      //最后默认是基础类型
      return [{ type, format, nullable, description }];
    }
  }

  /**获取swagger数据中的请求(分组)列表 */
  protected getSwaggerMethods() {
    const paths = (this.swg.paths || {}) as ISwgPaths;
    const mMethods: Array<ISwgMethod> = [];
    for (const p in paths) {
      const path = paths[p] as ISwgPath;
      for (const m in path) {
        const method = path[m] as ISwgMethod;
        method.path = p;
        method.method = m;
        method.group = (method.tags || [])[0];
        mMethods.push(method);
      }
    }
    return mMethods.group("group");
  }
  /**获取swagger数据中的模型定义数据 */
  protected getSwaggerDefines() {
    const { swg } = this;
    const { schemas } = swg.components || { schemas: swg.definitions };
    const result: Array<ISwgDefine> = [];
    if (!schemas) return result;
    for (const p in schemas) {
      const define = schemas[p];
      define.name = p;
      result.push(define);
    }
    return result;
  }

  /**获取请求方法名称 */
  protected getMethodName(parm: ISwgMethod): string {
    const pre = parm.method || "get";
    const actName = MBase.camelUpperCase(parm.operationId || "");
    const pres = "/api/services/app/,/api/".split(",");
    let path = parm.path;
    //去掉api前缀
    for (const p of pres) path = path.replace(new RegExp(`^${p}`, "i"), "");
    //去掉path中的参数
    let paths = path.split("/");
    paths = paths.map((x) =>
      x.replace(/{([^}]+)}/, MBase.camelUpperCase("$1"))
    );
    paths = paths.map((x) => MBase.camelUpperCase(x));
    //如果没有操作名，用operationId补全
    if (paths.length < 2) paths.push(actName);
    const res = MBase.snakeToCamel(`${pre}${paths.join("")}`);
    return MBase.camelLowercase(res);
  }

  /**获取请求方法参数 */
  protected getMethodParms(parms?: Array<ISwgParamter>): Array<IParm> {
    const ps = parms || [];
    const result: Array<IParm> = [];
    if (ps.length < 1) return result;
    const mParms = ps.filter((x) => x.in != "body" && x.in != "formData");
    for (const p of mParms) {
      const { description, required, nullable } = p;
      const name = MBase.camelLowercase(p.name || "");
      const inPath = p.in == "path";
      const type = this.getSchemaTypes(p);
      result.push({ name, type, inPath, description, required, nullable });
    }
    return result;
  }

  /**获取请求方法模型链 */
  protected getMethodBody(body?: ISwgBody | Array<ISwgParamter>): Array<IType> {
    if (!body) return [];
    if (body instanceof Array) return this.getMethodBodyByParms(body);
    return this.getMethodBodyByBody(body);
  }
  /**从requestBody中获取模型链 */
  protected getMethodBodyByBody(body: ISwgBody): Array<IType> {
    const content = body.content || {};
    const formData = content["multipart/form-data"];
    const json =
      content["application/*+json"] ||
      content["application/json"] ||
      content["text/json"];
    if (formData) return this.getSchemaTypes(formData!);
    if (json) return this.getSchemaTypes(json);
    return [];
  }
  /**从parameters中获取模型链 */
  protected getMethodBodyByParms(parms: Array<ISwgParamter>): Array<IType> {
    const mForm = parms.filter((x) => x.in == "formData");
    const mBody = parms.filter((x) => x.in == "body");
    if (mForm.length > 0) {
      return [{ type: "Map", description: "" }];
    } else if (mBody.length > 0) {
      return this.getSchemaTypes(mBody[0]);
    } else {
      return [];
    }
  }
  /**获取请求方法响应 */
  protected getMethodResponse(response?: ISwgResponse): Array<IType> {
    if (!response) return [];
    const content = response.content || {};
    const schema = response.schema || {};
    const res =
      content["application/*+json"] ||
      content["application/json"] ||
      content["text/json"] ||
      schema;
    return this.getSchemaTypes(res);
  }

  /**获取泛型基础组合名称 */
  protected getBaseGenericName(name?: string) {
    if (!name) return "";
    const matchFull = name.match(/^(.+?)(`\d+)\[\[(.+)\]\]$/);
    const matchBase = name.match(/^(.+?)(`\d+)?\[(.+)\]$/);
    if (!matchFull && !matchBase) return name.split(".").pop() || name;
    const [all, fullBaseName, dDount, genericArgs] = matchFull || matchBase!;
    const baseName = fullBaseName.split(".").pop() || fullBaseName;
    const args = genericArgs
      .split("],[")
      .map((x) => x.replace(/^\[|\]$/g, ""))
      .map((x) => {
        const [fullTypeName] = x.replace(/\s+/, "").split(",");
        return fullTypeName.split(".").pop() || fullTypeName;
      });
    return `${baseName}${args.join("")}`;
  }

  /**获取模型链中被使用的模型 */
  protected getUsedTypes(types: Array<IType>): Array<IParsedUse> {
    const base1 = "int,integer,int32,int64";
    const base2 = "number,double,float,decimal,long,short";
    const base3 = "string,boolean,file,blob,array,object";
    const bases = `${base1},${base2},${base3}`.split(",");
    const enums = (this.enums || []).map((x) => x.name);
    return (types || [])
      .filter((x) => !!x.type && !bases.includes(x.type))
      .map((x) => ({
        name: x.type,
        file: x.type,
        isEnum: enums.includes(x.type),
      }));
  }
  /**枚举类型使用兼容处理 */
  private fixEnumTypes() {
    const { models, methods } = this;
    //模型属性类型中，枚举类型标记
    models.forEach(({ props }) =>
      (props || []).forEach(({ type }) =>
        (type || []).forEach((x) => this.signEnumType(x))
      )
    );
    //请求函数中，枚举类型标记
    methods.forEach(({ funcs }) =>
      (funcs || []).forEach(({ inputParms, inputModel, outputModel }) => {
        //传入参数
        (inputParms || []).forEach(({ type }) =>
          (type || []).forEach((x) => this.signEnumType(x))
        );
        //传入模型
        inputModel.forEach((x) => this.signEnumType(x));
        //返回模型
        outputModel.forEach((x) => this.signEnumType(x));
      })
    );
  }
  /**标记枚举类型 */
  private signEnumType(type: IType) {
    const enums = this.enums || [];
    const typeStr = type.type;
    const desc = type.description || "";
    const enumNames = enums.map((x) => x.name);
    if (!enumNames.includes(typeStr)) return;
    const entity = (enums.find((x) => x.name === typeStr) || {}) as IParsedEnum;
    const enumType = entity.type;
    const items = (entity.props || []).map((x) => x.key).join(",");
    type.isEnum = true;
    type.enumType = enumType;
    type.description = desc ? `${desc}||${items}` : items;
  }
}
